package cmd

import (
	"errors"
	"fmt"
	"gitee/zeqjone/gen/conf"
	"gitee/zeqjone/gen/lib/utils"
	"gitee/zeqjone/gen/repo"
	"os"
	"os/exec"
	"path"
	"strings"

	"github.com/mitchellh/go-homedir"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	cfgFile = ""
	// 默认配置文件的名字
	configFileName = "gen"
)

func init() {
	cobra.OnInitialize(initConfig)

	rootCmd.PersistentFlags().StringVar(&cfgFile, "cfgfile", "", "设置配置文件地址，默认在用户home目录下")
}

// initConfig 工具初始化，并写入配置文件
func initConfig() {
	if cfgFile == "" {
		home, err := homedir.Dir()
		cobra.CheckErr(err)
		viper.AddConfigPath(home)
		viper.SetConfigName(configFileName)
		viper.SetConfigType("yaml")

		_, err = os.Open(path.Join(home, configFileName))
		if err != nil {
			if errors.Is(err, os.ErrNotExist) {
				_, err := os.Create(path.Join(home, configFileName))
				if err != nil {
					fmt.Println(err)
				}
			}
			if errors.Is(err, os.ErrPermission) {
				fmt.Printf("当前账户创建 %s 权限不足", path.Join(home, "gen"))
				return
			}
		}
	} else {
		// use config file from flags
		viper.SetConfigFile(cfgFile)
		viper.SetConfigType("yaml")
	}

	viper.AutomaticEnv()

	if err := viper.ReadInConfig(); err == nil {
		fmt.Printf("Using config file: %s\n", viper.ConfigFileUsed())
		f, err := os.Open(viper.ConfigFileUsed())
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		err = viper.ReadConfig(f)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		dsn := viper.GetString(conf.MysqlDsn)
		if dsn == "" {
			fmt.Println("第一次使用请先指定 gen init 配置数据库连接字符串， 否则不能使用")
			return
		}
		repo.NewDB(&repo.MysqlCfg{
			Dsn: dsn,
		})
	} else {
		fmt.Println(err.Error())
	}
}

var rootCmd = &cobra.Command{
	Use:   "gen",
	Short: "to gen to struct file from mysql",
	Long:  "根据 mysql 数据库的表结构，生成 go 结构体文件",
	Run: func(c *cobra.Command, args []string) {
		fmt.Printf("args: %v", args)
	},
}

func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Fprintln(os.Stdout, err)
		os.Exit(1)
	}
}

// fmtTableModel 格式化生成的 go 文件
func fmtTableModel(f string) {
	cmd := exec.Command("goimports", "-w", f)
	err := cmd.Run()
	if err != nil {
		fmt.Printf("fmt err22: %v\n\n", err)
	}
}

// SaveGoStruct 将表接口映射到 go struct
func SaveGoStruct(tbls []*repo.Table) {
	strmodel := &strings.Builder{}
	strfunc := &strings.Builder{}
	strmodel.WriteString("// desc: code generate by tools\n")
	strmodel.WriteString("// version: v1.0.0\n\n")
	strmodel.WriteString(fmt.Sprintf("package %s\n", viper.GetString(conf.OutputNameSpace)))
	strfunc.WriteString(strmodel.String())
	strmodel.WriteString(`
	import (
	"w.src.corp.qihoo.net/library/zorm"
	)

	var (
		gormdb *gorm.DB
		zormdb *zorm.DBDao
	)
	
	type ITable interface {
		// 获取表名，GORM要用到
		TableName() string
		// 获取表名，ZORM要用到
		GetTableName() string
		// 获取数据库主键信息
		GetPKColumnName() string
		// 获取数据库序列
		GetPkSequence() string
		// 获取主键ID的值
		GetID() interface{}
		// 检查当前对象是不是没有id，没有id则插入，有id 则更新
		CheckEmptyId() bool
		// 检查表是否有租户字段（非通用方法，大项目定制方法）
		HasLesseeCol() bool
	}
	
	// Init 使用这个ORM之前，一定要先初始化！！！
	func Init(gdao *gorm.DB, zdao *zorm.DBDao) {
		if gdao == nil && zdao == nil {
			panic(errors.New("参数不能都为空！"))
		}
		if gdao != nil {
			gormdb = gdao
		}
		if zdao != nil {
			zormdb = zdao
		}
	}
	// GetStructFieldTagInfo 获取指定结构体上的tag 信息
	func GetStructFieldTagInfo(t interface{}, tagname string) []string {
	       var slice []string
	       reft := reflect.TypeOf(t)
	       if reft == nil {
	               return nil
	       }
	       if tt, ok := t.(reflect.Type); ok {
	               reft = tt
	       }
	       if reft.Kind() == reflect.Struct {
	               num := reft.NumField()
	               for i := 0; i < num; i++ {
	                       tn := reft.Field(i).Tag.Get(tagname)
	                       if tn != "" {
	                               slice = append(slice, reft.Field(i).Tag.Get(tagname))
	                       }
	               }
	       }
	       if reft.Kind() == reflect.Ptr {
	               return GetStructFieldTagInfo(reft.Elem(), tagname)
	       }
	       return slice
	}
	
	func GetStructFieldVal(t ITable, isPk bool) []interface{} {
	       var slice []interface{}
	       refv := reflect.ValueOf(t)
	       reft := reflect.TypeOf(t)
	       pk := t.GetPKColumnName()
	
	       if refv.Kind() == reflect.Ptr {
	               refv = refv.Elem()
	               reft = reft.Elem()
	       }
	       if refv.Kind() == reflect.Struct {
	               num := refv.NumField()
	               for i := 0; i < num; i++ {
	                       fn := reft.Field(i).Name
	                       if !isPk && strings.EqualFold(fn, pk) {
	                               continue
	                       }
	                       if fn != "" {
	                               slice = append(slice, refv.Field(i).Interface())
	                       }
	               }
	       }
	       return slice
	}
	
	type GenField struct {
		Col string
		Val interface{}
	}
	`)
	for _, t := range tbls {
		fmt.Printf("t:%s\n", t.Name)
		pascalName := utils.Snake2Pascal(t.Name)
		strmodel.WriteString(fmt.Sprintf("// %s %s\n", pascalName, t.Comment))
		strmodel.WriteString(fmt.Sprintf("type %s struct {\n", pascalName))
		for _, c := range t.Cols {
			cs := GetColDesp(c)
			strmodel.WriteString(cs)
		}
		strmodel.WriteString("}\n")
		strFunc := GetTableNameFunc(t)
		strfunc.WriteString(strFunc)
	}
	strmodel.WriteString(DBMethods())
	strmodel.WriteString("\n\n")
	d := viper.GetString(conf.OutputDir)
	fname := viper.GetString(conf.OutputNameSpace)
	fmodel, err := os.Create(fmt.Sprintf("%s/%s_model.go", d, fname))
	if err != nil {
		panic(fmt.Errorf("创建文件失败：%v", err))
	}
	defer fmodel.Close()
	fmodel.WriteString(strmodel.String())
	fmtTableModel(fmodel.Name())

	fmothods, err := os.Create(fmt.Sprintf("%s/%s_methods.go", d, fname))
	if err != nil {
		panic(fmt.Errorf("创建文件失败：%v", err))
	}
	defer fmothods.Close()
	fmothods.WriteString(strfunc.String())
	fmtTableModel(fmothods.Name())
	fmt.Println("table model file finished")
}

// GetColDesp 将数据库的表字段映射到结构体的字段
func GetColDesp(col repo.Column) string {
	cs := ""
	orm := viper.GetString(conf.MysqlOrm)
	fmt.Println(orm, col)
	if repo.GenOrm(orm) == repo.GenOrmGorm {
		colName := utils.Snake2Pascal(col.Name)
		if colName == "Id" {
			colName = "ID"
		}
		tags := ""
		if colName == "ID" || strings.ToLower(col.Key) == "pri" {
			tags = fmt.Sprintf(`gorm:"%scolumn:%s" column:"%s"`, "primaryKey;", col.Name, col.Name)
		} else if strings.ToLower(col.Key) == "uni" {
			tags = fmt.Sprintf(`gorm:"%scolumn:%s" column:"%s"`, "unique;", col.Name, col.Name)
		} else {
			tags = fmt.Sprintf(`gorm:"%scolumn:%s" column:"\"%s\""`, "", col.Name, strings.ToUpper(col.Name))
		}
		jsonName := utils.Camel2Snake(col.Name)
		cs = fmt.Sprintf("%s %s `json:\"%s\" %s`", colName, repo.GetGoType(col.Type), jsonName, tags)
		if col.Comment != "" {
			cs += fmt.Sprintf(" // %s\n", col.Comment)
		} else {
			cs += "\n"
		}
	}
	return cs
}

// GetTableNameFunc 生成的结构体方法
func GetTableNameFunc(t *repo.Table) string {
	tablename := fmt.Sprintf("func (ins *%s) TableName () string {\n return \"%s\"\n}", utils.Snake2Pascal(t.Name), t.Name)
	getTableName := ""
	// GetTableName 这个方法在返回数据库表名的时候，是否携带返回schema的名字
	if strings.ToLower(viper.GetString(conf.TableNameWithSchema)) == "true" {
		getTableName = fmt.Sprintf(`func(ins *%s) GetTableName() string{
			 return fmt.Sprintf("%%s%%s", %s, %s) 
		}`,
			utils.Snake2Pascal(t.Name),
			strings.ToUpper(t.Schema)+".",
			strings.ToUpper(t.Name),
		)
	} else {
		getTableName = fmt.Sprintf(`func(ins *%s) GetTableName() string{
			return "\"%s\""
		}`,
			utils.Snake2Pascal(t.Name),
			strings.ToUpper(t.Name),
		)
	}
	pks := make([]string, 0)
	for _, c := range t.Pks {
		pks = append(pks, c.Name)
	}
	pk := strings.Join(pks, ",")
	if pk == "" {
		pk = "id"
	}
	// todo: 如果是组合主键，该怎么返回: 暂不支持
	tn := utils.Snake2Pascal(t.Name)
	structId := utils.Snake2Pascal(pk)
	if structId == "Id" {
		structId = "ID"
	}
	getPKColumnName := fmt.Sprintf("func(ins *%s) GetPKColumnName()string{\n return \"%s\"\n}", tn, pk)
	getPkSequence := fmt.Sprintf("func(ins *%s) GetPkSequence()string{\n return \"\"\n}", tn)
	getGetId := fmt.Sprintf("func(ins *%s) GetID()interface{}{\n return ins.ID\n}", tn)
	checkEmptyId := fmt.Sprintf(`func (ins *%s) CheckEmptyId() bool {
		if ins == nil {
			return true
		}
		return reflect.ValueOf(ins.%s).IsZero()
	}`, tn, structId)
	cols := make([]string, 0, len(t.Cols))
	for _, c := range t.Cols {
		cols = append(cols, c.Name)
	}
	hasLesseeCol := fmt.Sprintf(`func (ins *%s) HasLesseeCol() bool {
	if ins == nil {
		return false
	}
	return %v
	}`, tn, utils.Has(cols, "channel"))

	return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n", tablename, getTableName, getPKColumnName,
		getPkSequence, getGetId, checkEmptyId, hasLesseeCol)
}

func DBMethods() string {
	// 下面开始适配ROM
	getById := `func DBGetById(ctx context.Context, ins ITable,  id interface{}) error {
			if gormdb != nil {
				err := gormdb.WithContext(ctx).Model(ins).Where(ins.GetPKColumnName(), id).First(ins).Error
				if errors.Is(err, gorm.ErrRecordNotFound) {
					err = nil
				}
				return err
			}
			finder := zorm.NewSelectFinder(ins.GetTableName()).Append("WHERE "+ins.GetPKColumnName()+" = ?", id)
			has, err := zorm.QueryRow(ctx, finder, ins)
			if !has {
				return nil
			}
			return err
		}`
	getOneByField := `func DBGetOneByField(ctx context.Context, ins ITable, colName string, field interface{}) error {
			if gormdb!=nil {
				err := gormdb.Model(ins).Where(colName, field).Find(ins).Error; 
				if errors.Is(err, gorm.ErrRecordNotFound) {
					err = nil
				}
				return err
			}
			finder := zorm.NewSelectFinder(ins.GetTableName()).Append("WHERE "+colName+" = ?", field)
			has, err := zorm.QueryRow(ctx, finder, ins)
			if !has {
				return nil
			}
			return err
		}`
	getAllByField := `func DBGetAllByField[T ITable](ctx context.Context,ins T, args ...*GenField) ([]T, error){
			list:=make([]T, 0)
			if gormdb != nil {
				m:=gormdb.WithContext(ctx).Model(ins)
				for _,nf :=range args {
					m = m.Where(nf.Col, nf.Val)
				}
				err:=m.Find(&list).Error
				return list, err
			}
			finder := zorm.NewSelectFinder(ins.GetTableName())
			if len(args) > 0 {
				finder = finder.Append(" where ");
				strs := make([]string, 0)
				vals := make([]interface{}, 0)
				for _, p := range args {
					strs = append(strs, p.Col + "=?")
					vals = append(vals, p.Val)
				}
				finder = finder.Append(strings.Join(strs, " AND "), vals...);
			}
			err := zorm.Query(ctx, finder, &list, nil)
			return list, err
		}
		`
	getPageByField := `func DBGetPageByField[T ITable](ctx context.Context, ins T, page, pageSize int,orderby string, args ...*GenField) ([]T, error){
			list:=make([]T, 0)
			zp := zorm.NewPage()
			zp.PageNo = page
			zp.PageSize = pageSize
			if zp.PageNo <= 0 {
				zp.PageNo = 1
			}
			if zp.PageSize <0 {
				zp.PageSize = 20
			}
			if gormdb != nil {
				m:=gormdb.WithContext(ctx).Model(ins)
				for _,nf :=range args {
					m = m.Where(nf.Col, nf.Val)
				}
				if orderby != "" {
					m = m.Order(orderby)
				}
				limit := zp.PageSize
				start := (zp.PageNo - 1) * limit
				err := m.Offset(start).Limit(limit).Find(&list).Error
				return list, err
			}
			finder := zorm.NewSelectFinder(ins.GetTableName())
			if len(args) > 0 {
				finder = finder.Append(" where ");
				strs := make([]string, 0)
				vals := make([]interface{}, 0)
				for _, p := range args {
					strs = append(strs, p.Col + "=?")
					vals = append(vals, p.Val)
				}
				finder = finder.Append(strings.Join(strs, " AND "), vals...);
			}
			err := zorm.Query(ctx, finder, &list, zp)
			return list, err
		}`
	dbCreate := `func DBCreate(ctx context.Context, ins ITable) error{
			if gormdb != nil {
				return gormdb.WithContext(ctx).Create(ins).Error
			}
			_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
				_, err := zorm.Insert(ctx, ins)
				return nil, err
			})
			return err
		}`
	dbBatchCreate := `func DBBatchCreate[T ITable](ctx context.Context, models []T) error {
		var (
			offset, limit, ln = 0, 1000, len(models)
		)
		if gormdb != nil {
			for offset < ln {
				end := offset + limit
				if end > ln {
					end = ln
				}
				err := gormdb.WithContext(ctx).Create(models[offset:end]).Error
				if err != nil {
					return err
				}
				offset = end
			}
			return nil
		}
		_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
			mk := make([]zorm.IEntityStruct, 0, limit)
			for _, m := range models {
				mk = append(mk, m)
				if len(mk) >= limit {
					_, err := zorm.InsertSlice(ctx, mk)
					if err != nil {
						return nil, err
					}
					mk = mk[:0]
				}
			}
			if len(mk) > 0 {
				return zorm.InsertSlice(ctx, mk)
			}
			return nil, nil
		})
		return err
	}
		`
	dbSave := `func DBSave(ctx context.Context, ins ITable) error {
			if gormdb != nil {
				return gormdb.WithContext(ctx).Save(ins).Error
			}
			_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error){
				if ins.CheckEmptyId() {
					_, err := zorm.Insert(ctx, ins)
					return nil, err
				}
				_, err := zorm.Update(ctx, ins)
				return nil, err
			})
			return err
		}`
	dbBatchSave := `func DBBatchSave[T ITable](ctx context.Context, unikeys []string, updatekeys []string, models []T) error {
	if len(models) == 0 || len(unikeys) == 0 || len(updatekeys) == 0 {
		return nil
	}
	ins := models[0]
	if gormdb != nil {
		return gormdb.WithContext(ctx).Model(ins).Save(models).Error
	}
	pk := ins.GetPKColumnName()
	isPk := len(unikeys) == 1 && strings.EqualFold(unikeys[0], pk)
	str := new(strings.Builder)
	ftags := GetStructFieldTagInfo(ins, "column")
	var selcols, allcols, allvals, uniquekeys, updateset []string
	var pms []interface{}
	for _, k := range ftags {
		if !isPk && strings.EqualFold(k, pk) {
			continue
		}
		selcols = append(selcols, fmt.Sprintf(" ? as %s", k))
		allcols = append(allcols, k)
		allvals = append(allvals, "?")
	}
	strvals := strings.Join(allvals, ",")

	for _, k := range unikeys {
		k2 := strings.ToUpper(k)
		uniquekeys = append(uniquekeys, fmt.Sprintf("t1.%s = t2.%s", k2, k2))
	}
	for _, k := range updatekeys {
		k2 := strings.ToUpper(k)
		updateset = append(updateset, fmt.Sprintf("t1.%s = t2.%s", k2, k2))
	}
	str.WriteString(fmt.Sprintf("merge into %s as t1 ", ins.GetTableName()))
	str.WriteString("using (")
	for i := 0; i < len(models); i++ {
		if i == 0 {
			str.WriteString(fmt.Sprintf("select %s from dual ", strings.Join(selcols, ", ")))
		} else {
			str.WriteString(fmt.Sprintf(" union all select %s from dual", strvals))
		}
		pms = append(pms, GetStructFieldVal(models[i], isPk)...)
	}
	str.WriteString(fmt.Sprintf(") as t2 on (%s) ", strings.Join(uniquekeys, " and ")))
	str.WriteString(fmt.Sprintf(" when matched then update set %s", strings.Join(updateset, ", ")))
	str.WriteString(fmt.Sprintf(" when not matched then insert (%s) values (t2.%s)", strings.Join(allcols, ","), strings.Join(allcols, ", t2.")))
	finder := zorm.NewFinder().Append(str.String(), pms...)
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		_, err := zorm.UpdateFinder(ctx, finder)
		return nil, err
	})
	return err
}`
	dbUpdateCol := `func DBUpdateCol(ctx context.Context, ins ITable, col string, val interface{}) error {
			if gormdb != nil {
				return gormdb.WithContext(ctx).Model(ins).Update(col, val).Error
			}
			_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error){
				finder := zorm.NewUpdateFinder(ins.GetTableName())
				finder.Append(fmt.Sprintf("%s = ?", col), val).Append(" where id = ?", ins.GetID())
				_, err := zorm.Update(ctx, ins)
				return nil, err
			})
			return err
		}`
	dbUpdateMaps := `func DBUpdateWithMaps(ctx context.Context, ins ITable, mp map[string]interface{}) error {
			if gormdb != nil {
				return gormdb.WithContext(ctx).Model(ins).Updates(mp).Error
			}
			_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error){
				entitymp := zorm.NewEntityMap(ins.GetTableName())
				entitymp.PkColumnName = ins.GetPKColumnName()
				for k, v := range mp {
					entitymp.Set(k, v)
				}
				entitymp.Set(entitymp.PkColumnName, ins.GetID())
				_, err := zorm.UpdateEntityMap(ctx, entitymp)
				return nil, err
			})
			return err
		}`

	dbUpdatebatchMaps := `func DBUpdateBatchMaps(ctx context.Context, ins ITable, sql string, val []interface{}, mp map[string]interface{}) error {
			if gormdb != nil {
				return gormdb.WithContext(ctx).Table(ins.TableName()).Where(sql, val...).Updates(mp).Error
			}
			_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
				finder := zorm.NewUpdateFinder(ins.GetTableName())
				cols := make([]string, 0, len(mp))
				vals := make([]interface{}, 0, len(mp))
				for k, v := range mp {
					cols = append(cols, k+" = ?")
					vals = append(vals, v)
				}
				vals = append(vals, val...)
				finder.Append(strings.Join(cols, ",")+" where "+sql, vals...)
				cnt, err := zorm.UpdateFinder(ctx, finder)
				return cnt, err
			})
			return err
		}`
	dbUpdateByExpSetMaps := `func DBUpdateByExpSetMaps(ctx context.Context, ins ITable, e *exp.Exp, mp map[string]interface{}) error {
			if gormdb != nil {
				str, args := build(e)
				return gormdb.WithContext(ctx).Table(ins.TableName()).Where(str, args...).Updates(mp).Error
			}
			str, args := buildDm(e)
			_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
				finder := zorm.NewUpdateFinder(ins.GetTableName())
				cols := make([]string, 0, len(mp))
				vals := make([]interface{}, 0, len(mp))
				for k, v := range mp {
					cols = append(cols, k+" = ?")
					vals = append(vals, v)
				}
				vals = append(vals, args...)
				finder.Append(strings.Join(cols, ",")).Append(str, vals...)
				cnt, err := zorm.UpdateFinder(ctx, finder)
				return cnt, err
			})
			return err
		}`
	dbDelete := `func DBDelete(ctx context.Context, ins ITable) error {
			if gormdb != nil {
				return gormdb.WithContext(ctx).Delete(ins).Error; 
			}
			_,err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
				_, err := zorm.Delete(ctx, ins)
				return nil, err
			})
			return err
		}`
	dbDeleteByField := `func DBDeleteByField[T ITable](ctx context.Context,ins T, colCondition string, val interface{}) error {
			if gormdb != nil {
				return gormdb.WithContext(ctx).Where(colCondition, val).Delete(ins).Error
			}
			_,err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
				finder := zorm.NewDeleteFinder(ins.GetTableName())
				finder.Append("where " + colCondition + "= ?", val)
				_, err := zorm.UpdateFinder(ctx, finder)
				return nil, err
			})
			return err
		}`

	getAllByExp := `func DBGetAllByExp[T ITable](ctx context.Context, ins T, e *exp.Exp) ([]T, error) {
	list := make([]T, 0)
	if gormdb != nil {
		str, args := build(e)
		dbaction := gormdb.WithContext(ctx).Model(ins)
		if len(args) > 0 {
			dbaction = dbaction.Where(str, args...)
		}
		err := dbaction.scan(&list).Error
		return list, err
	}
	str, args := buildDm(e)
	finder := zorm.NewSelectFinder(ins.GetTableName())
	if len(args) > 0 {
		finder = finder.Append(str, args...)
	}
	err := zorm.Query(ctx, finder, &list, nil)
	return list, err
}`

	getPageByExp := `func DBGetPageByExp[T ITable](ctx context.Context, ins T, e *exp.Exp, offset, limit int, orderby string) ([]T, error) {
	list := make([]T, 0)
	if gormdb != nil {
		str, args := build(e)
		m := gormdb.WithContext(ctx).Model(ins).Where(str, args...)
		if orderby != "" {
			m = m.Order(orderby)
		}
		err := m.Offset(offset).Limit(limit).Find(&list).Error
		return list, err
	}
	zp := zorm.NewPage()
	zp.PageNo = offset/limit + 1 
   	zp.PageSize = limit
	finder := zorm.NewSelectFinder(ins.GetTableName())
	str, args := buildDm(e)
	if len(args) > 0 {
		finder = finder.Append(str, args...)
	}
	if orderby != "" {
		finder = finder.Append(fmt.Sprintf(" order by %s", orderby))
	}
	err := zorm.Query(ctx, finder, &list, zp)
	return list, err
	}`

	getCountByExp := `func DBCountByExp[T ITable](ctx context.Context, ins T, e *exp.Exp) (int64, error) {
	var cnt int64
	if gormdb != nil {
		str, args := build(e)
		m := gormdb.WithContext(ctx).Model(ins)
		if len(args) > 0 {
			m = m.Where(str, args...)
		}
		err := m.Count(&cnt).Error
		return cnt, err
	}
	finder := zorm.NewSelectFinder(ins.GetTableName(), "count(*)")
	str, args := buildDm(e)
	if len(args) > 0 {
		finder = finder.Append(str, args...)
	}
	_, err := zorm.QueryRow(ctx, finder, &cnt)
	return cnt, err
	}`

	deleteByExp := `func DBDeleteByExp[T ITable](ctx context.Context, ins T, e *exp.Exp) error {
		if gormdb != nil {
			str, args := build(e)
			if str != "" {
				return gormdb.WithContext(ctx).Where(str, args...).Delete(ins).Error
			}
 			return gormdb.WithContext(ctx).Delete(ins).Error
		}
		_,err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
			finder := zorm.NewDeleteFinder(ins.GetTableName())
			str, args := buildDm(e)
			if str != "" {
				finder.Append(str, args...)
			}
			_, err := zorm.UpdateFinder(ctx, finder)
			return nil, err
		})
		return err
		}`

	getOneByExp := `func DBGetOneByExp[T ITable](ctx context.Context, ins T, e *exp.Exp, orderby string) (err error) {
		if gormdb != nil {
			str, args := build(e)
			db := gormdb.WithContext(ctx).Model(ins)
			if str != "" {
				db = db.Where(str, args...)
			}
			if orderby != "" {
				db = db.Order(orderby)
			}
			err = db.First(ins).Error
			if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
				err = nil
			}
			return err
		}
		finder := zorm.NewSelectFinder(ins.GetTableName())
		str, args := buildDm(e)
		if str != "" {
			finder.Append(str, args...)
		}
		if orderby != "" {
			finder.Append(" order by " + orderby)
		}
		finder.Append(" limit 1")
		has, err := zorm.QueryRow(ctx, finder, ins)
		if !has {
			return nil
		}
		return
	}`

	dbRawQuery := `func DBRawQuery[D any](ctx context.Context, sql string, ex exp.Exp) ([]D, error) {
	var result []D
	if gormdb != nil {
		sqlw, args := exp.Build(ex)
		err := gormdb.Raw(sql).Where(sqlw, args...).Scan(&result).Error
		return result, err
	}
	queryFinder := zorm.NewFinder()
	queryFinder.Append(sql)
	sqlw, args := exp.BuildDm(ex)
	queryFinder = queryFinder.Append(sqlw, args...)
	err := zorm.Query(ctx, queryFinder, &result, nil)
	return result, err
}`

	list2map := `
	// ！！！慎用：有类型断言，不如直接写for循环。但是对于数据量不是很大的情况，可以用一下！！
	func ListToMap[T ITable](slice []T) map[int]T {
		mp := make(map[int]T)
		for _, l := range slice {
			id := l.GetID()
			if t, ok := id.(int); ok {
				mp[t] = l
			}
		}
		return mp
	}
	
	
type d[T UniqueStableType] struct {
	t       T
	hasNext int
}

type UniqueStableType interface {
	ITable
}

// UniqueStable mapKey() 唯一key uniqueStrategy 默认保留第一个，last 则保留最后一个
func UniqueStable[T UniqueStableType](slice []T, mapKey func(t T) string, uniqueStrategy string) []T {

	inResult := make(map[string]*d[T])
	var result []T
	for _, v := range slice {
		key := mapKey(v)
		if _, ok := inResult[key]; ok {
			inResult[key].hasNext++
			if uniqueStrategy == "last" {
				inResult[key].t = v
			}
		} else {
			inResult[key] = &d[T]{
				t:       v,
				hasNext: 1,
			}
		}
	}

	for _, v := range slice {
		key := mapKey(v)
		if x, _ := inResult[key]; x.hasNext == 1 {
			result = append(result, x.t)
		} else {
			if x.hasNext == 0 {
				continue
			}
			if uniqueStrategy == "last" {
				x.hasNext--
			} else { // 保留第一个
				result = append(result, x.t)
				x.hasNext = 0
			}
		}
	}
	return result
}

	`

	return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n %s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", getById, getOneByField, getAllByField, getPageByField,
		dbCreate, dbBatchCreate, dbSave, dbBatchSave, dbUpdateCol, dbUpdateMaps, dbUpdatebatchMaps, dbUpdateByExpSetMaps, dbDelete, dbDeleteByField,
		getAllByExp, getPageByExp, getCountByExp, deleteByExp, getOneByExp, dbRawQuery, list2map)
}
